home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 050 / flush12.arc / FLUSH12.ACC
Text File  |  1986-11-28  |  13KB  |  283 lines

  1. *******************************************************************************
  2.  
  3.                                    FLUSH.ACC
  4.                                   Version 1.2
  5.                                January 29, 1986
  6.                                by Randy Forgaard
  7.                              CompuServe 70307,521
  8.  
  9. This file, FLUSH.ACC, is related to the file FLUSH.PAS in DL 1 of the Borland
  10. SIG on CompuServe, but FLUSH.PAS is not needed in order to use the routines
  11. below.
  12.  
  13. This file is for use with the Turbo Access portion of the MS-DOS Turbo
  14. (Database) Toolbox version 1.1, together with MS-DOS or PC-DOS Turbo Pascal
  15. 3.01A or higher (regular, 8087, or BCD version).  If you have version 1.0 of
  16. the Toolbox, see the file TBXFIX in DL 1 of the Borland SIG for the source code
  17. changes that will upgrade your copy to version 1.1.  If you have version 3.00A
  18. or 3.00B of Turbo Pascal, arrange with Borland Customer Service to upgrade your
  19. copy of Turbo to 3.01A or higher (3.00A and 3.00B are not compatible with Turbo
  20. Database Toolbox).  The routines below will not work with earlier (pre-3.0)
  21. versions of Turbo Pascal, nor with Turbo Pascal for operating systems other
  22. than DOS.
  23.  
  24. This file describes certain changes that you can make to the source code of
  25. Turbo Access to make your database virtually impervious to system crashes.
  26. Normally, when you use Turbo Access and you use MakeFile, AddRec, PutRec,
  27. DeleteRec, MakeIndex, AddKey, or DeleteKey to create or change a DataFile or
  28. IndexFile, not all of the changes you make are necessarily written to the disk
  29. right away.  Typically, the most recent changes are kept in memory buffers by
  30. Turbo Access, the Turbo run-time system, and/or DOS.  This technique allows
  31. file output to proceed more quickly, since an entire index page or disk sector
  32. can be written to the disk at once.  When a file gets closed, its memory
  33. buffers are all written to disk.  However, if the system should crash (due to
  34. program error, accidental Ctrl-Alt-Del, power failure, tripping over the plug,
  35. etc.) after a file has been changed and before it gets closed, the recent
  36. changes that are in memory but not on the disk will not get written.  Even more
  37. insidious, a system crash can mean that the file length in the directory entry,
  38. or the record numbers in IndexFiles, do not get written to disk, rendering the
  39. information in the database inconsistent and probably unusable.
  40.  
  41. One can guard against system crashes by flushing all information in a file's
  42. memory buffers out to disk, every time that the contents of that file changes.
  43. Unfortunately, using the routines provided by Turbo Access, a DataFile or
  44. IndexFile can only be flushed by closing and reopening the file.  This is
  45. primarily because DOS provides no explicitly documented way to flush a file's
  46. buffers without closing/reopening.  Closing and reopening a file every time its
  47. contents change is extraordinarily expensive at run-time, largely because DOS
  48. must perform a directory lookup every time a file is reopened.
  49.  
  50. Fortunately, there actually IS a little-known method for flushing a file's
  51. buffers and updating the file length in the file's directory entry under DOS,
  52. without reopening the file.  The technique for "flushing" a file is: 1) invoke
  53. DOS function 45H, "Duplicate a File Handle (DUP)," to duplicate the file
  54. handle, and then 2) invoke DOS function 3EH, "Close a File Handle" to close the
  55. extra file handle that you just created (this action does not close the
  56. original file handle).  The "Close" function 3EH flushes the file's buffers, as
  57. documented in the DOS Technical Reference manual.  Yet the original file handle
  58. is still valid and usable, so the file does not need to be reopened.  Thanks
  59. for this clever technique go to Dan Daetwyler in a letter to Ray Duncan's
  60. 16-Bit Software Toolbox column in the December '85 issue of Dr. Dobb's Journal.
  61.  
  62. One of the routines below is a new routine for use with Turbo Access, called
  63. FlushFile.  This procedure will perform an actual flush on any DataFile, so
  64. that the DataFile will be completely up-to-date and consistent even if the
  65. system should crash after the flush.  The FlushFile routine uses the technique
  66. described above.  Note: The FLUSH.PAS file, mentioned above, contains two
  67. routines for flushing standard Turbo files (rather than Turbo Access files).
  68. The FlushAny procedure in FLUSH.PAS is very similar to the FlushFile procedure
  69. below.  The differences are that FlushFile updates the internal Record 0
  70. maintained by Turbo Access, and that FlushFile uses TaIOcheck, the internal
  71. Turbo Access routine, to report errors, rather than writing the errors directly
  72. to the primary output.
  73.  
  74. Just as FlushFile will flush any DataFile, FlushIndex (the other procedure
  75. below) will flush any IndexFile.  The FlushIndex code is similar to the
  76. CloseIndex routine included with Turbo Access, with the following differences:
  77. the index pages are not removed from the page stack after they are written to
  78. disk (which can make subsequent index searches proceed faster), and the final
  79. call to CloseFile is replaced by a call to FlushFile.
  80.  
  81. After making the Turbo Access source code changes indicated below, FlushFile
  82. and FlushIndex may be called explicitly by the calling program whenever
  83. desired.  Alternatively, the calling program may set the global Boolean
  84. variable FailSafe to "true," and the (modified) Turbo Access routines will
  85. automatically invoke FlushFile or FlushIndex (depending on context) whenever a
  86. DataFile or IndexFile is changed.  By default, FailSafe is "false."  If you
  87. decide to set FailSafe to "true," it should be done immediately after the call
  88. to InitIndex at the beginning of your main program; i.e.:
  89.  
  90.       ...
  91.       InitIndex;
  92.       FailSafe := true;
  93.       ...
  94.  
  95. With the changes below, the only possibility that data can become corrupted due
  96. to a system crash is if the crash occurs between when the file gets changed and
  97. when FlushFile or FlushIndex is automatically (or explicitly) invoked
  98. immediately afterward.  That window of vulnerability is a small number of
  99. microseconds, and occurs only at the actual moment that the program outputs a
  100. new data record or index key.  If your program is reading records, searching
  101. the database, waiting for user input, computing, printing, updating the screen,
  102. etc., your database is still safe if the system crashes.  However, if there
  103. should be a power failure at the exact moment that data is being written, it is
  104. likely that the failure will also cause serious problems on the disk as a
  105. whole, trashing the FAT or some directories.  Thus, in this very unlikely
  106. event, the disk may need to be restored from backup tape or disks in any case,
  107. and you probably shouldn't lose sleep over this possibility.
  108.  
  109. There are two important DISadvantages to using the Turbo Access code changes
  110. below, assuming you have set FailSafe to "true":
  111.  
  112.      1) MakeFile, AddRec, PutRec, DeleteRec, MakeIndex, AddKey, and DeleteKey
  113. will all execute somewhat slower, because of the automatic file flush.  This
  114. delay will not be nearly as great as closing/reopening the file, but it can be
  115. noticeable if done frequently.  If your database application spends a
  116. significant amount of time updating records, you might want to test how much
  117. slower your application runs with FailSafe = "true" than with FailSafe =
  118. "false."  You can reduce the delay by living a little dangerously and running
  119. with FailSafe = "false," invoking FlushFile and FlushIndex explicitly at
  120. strategic points.
  121.  
  122.      2)  The file flushing code below only works with DOS.  If you plan to port
  123. your application to other operating systems later, you may want to build a more
  124. robust scheme for detecting and recovering from system crashes, since you won't
  125. be able take advantage of the crash protection below.
  126.  
  127. It would be wonderful if Turbo Access could be made to automatically flush file
  128. buffers under other operating systems, in addition to DOS.  If you find a way
  129. to do this, PLEASE PLEASE add this information to the FLUSH.ACC file
  130. accordingly, and re-upload to DL 1 of the Borland SIG!  For that matter, if you
  131. are feeling truly altruistic, you might want to make similar additions to
  132. FLUSH.PAS, too, showing how to flush normal Turbo files under other operating
  133. systems.
  134.  
  135. Many thinks to Rick Amerson (CompuServe 72477,1566) for his generous help in
  136. testing the code below, to Andy Miller (CompuServe 70357,3656) for pointing out
  137. that the routines below do not use any undocumented features of DOS function
  138. calls, and to Peter Thomas (CompuServe 75716,2377) for suggesting the
  139. efficiency improvement to FlushIndex.
  140.  
  141. Change Log:
  142.  
  143.   Version 1.1: Removed caveats about undocumented use of DOS functions, since
  144.                this file actually only uses documented features of DOS.
  145.  
  146.   Version 1.2: Slight changes to FlushIndex so that index pages do not get
  147.                removed from the page stack in memory when they get flushed to
  148.                disk.  Can make subsequent index searches faster, since those
  149.                index pages will not need to be re-read from disk.
  150.  
  151. *******************************************************************************
  152.  
  153.  
  154. The code changes to add "flush" capability to MS-DOS Turbo Access version 1.1
  155. are shown below.  Please BE SURE to make a backup copy of your Turbo Access
  156. source code before making these changes.  Note: When making the changes below,
  157. be sure to use ACCESS3.BOX, not ACCESS.BOX, since ACCESS.BOX is only for use
  158. with Turbo 2.0, and the changes below can only be used with Turbo 3.01A and
  159. higher.
  160.  
  161.  
  162.  
  163. STEP 1: Add the following declaration to ACCESS3.BOX, immediately prior to
  164.         TaIOcheck:
  165.  
  166.              const
  167.                FailSafe: Boolean = false;
  168.  
  169.  
  170.  
  171. STEP 2: While you're at it, fix the following minor bug (unrelated to flushing
  172.         file buffers) that may be in your version of ACCESS3.BOX: In procedure
  173.         TaIOcheck, change the statement "I := 0;" to be "I := 1;".  Borland has
  174.         been alerted about this bug.
  175.  
  176.  
  177.  
  178. STEP 3: Insert the following routine immediately after the routine TaIOcheck in
  179.         ACCESS3.BOX:
  180.  
  181.  
  182. {Flushes the buffers associated with the DataFile "DatF," and updates the file
  183.  length in the directory entry of "DatF," without closing "DatF."}
  184.  
  185. procedure FlushFile (var DatF: DataFile);
  186. var
  187.   handle: Integer absolute DatF; {File handle is the first word of a DataFile}
  188.   regs: record
  189.           case Integer of
  190.             1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
  191.             2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
  192.         end;
  193. begin
  194.   {Code from beginning of CloseFile}
  195.   DatF.Int2 := DatF.NumRec;
  196.   Move(DatF.FirstFree, TaRecBuf, 8);
  197.  
  198.   {Simulate a PutRec(DatF, 0, TaRecBuf) (to avoid FlushFile/PutRec recursion)}
  199.   Seek(DatF.F, 0);
  200.   BlockWrite(DatF.F, TaRecBuf, 1);
  201.   IOstatus := IOresult;
  202.   TaIOcheck(DatF, 0);
  203.  
  204.   {Flush DatF}
  205.   IOstatus := $F0;            {"Disk write error" I/O error...just in case}
  206.   regs.AH := $45;             {DOS function to duplicate a file handle}
  207.   regs.BX := handle;
  208.   MsDos(regs);
  209.   if Odd(regs.Flags) then     {Check if carry flag is set}
  210.     TaIOcheck(DatF, 0);
  211.   regs.BX := regs.AX;         {Put new file handle into BX}
  212.   regs.AH := $3E;             {Dos function to close a file handle}
  213.   MsDos(regs);
  214.   if Odd(regs.Flags) then     {Check if carry flag is set}
  215.     TaIOcheck(DatF, 0)
  216. end {FlushFile};
  217.  
  218.  
  219.  
  220. STEP 4: Insert the following routine immediately after the routine OpenIndex in
  221.         ACCESS3.BOX:
  222.  
  223.  
  224. {Flushes the buffers associated with the IndexFile "IdxF," and updates the file
  225.  length in the directory entry of "IdxF," without closing "IdxF."}
  226.  
  227. procedure FlushIndex (var IdxF: IndexFile);
  228. var
  229.   I: Integer;
  230. begin
  231.   {Similar to CloseIndex:}
  232.   for I := 1 to PageStackSize do
  233.     with TaPageStk[I] do
  234.       if (IndexFPtr = Addr(IdxF)) and Updated then
  235.       begin
  236.         TaPack(Page,IdxF.KeyL);
  237.  
  238.         {Simulate a PutRec(IdxF.DataF, PageRef, Page) (to avoid redundant
  239.          FlushFile calls)}
  240.         Seek(IdxF.DataF.F, PageRef);
  241.         BlockWrite(IdxF.DataF.F, Page, 1);
  242.         IOstatus := IOresult;
  243.         TaIOcheck(IdxF.DataF, PageRef);
  244.  
  245.         TaUnpack(Page,IdxF.KeyL);
  246.         Updated := false
  247.       end;
  248.   IdxF.DataF.Int1 := IdxF.RR;
  249.   FlushFile(IdxF.DataF)
  250. end {FlushIndex};
  251.  
  252.  
  253.  
  254. STEP 5: Also in ACCESS3.BOX, add the following line just before the "end" at
  255.         the end of the PutRec routine:
  256.  
  257.              if FailSafe then FlushFile(DatF);
  258.  
  259.  
  260.  
  261. STEP 6: Insert the following line just before the very last "end" statement at
  262.         the very end of the file ADDKEY.BOX:
  263.  
  264.              if FailSafe then FlushIndex(IdxF);
  265.  
  266.  
  267.  
  268. STEP 7: Also, insert the above line just before the very last "end" statement
  269.         at the very end of the file DELKEY.BOX.
  270.  
  271.  
  272.  
  273. STEP 8: Don't forget to add the line "FailSafe := true;", if desired, to your
  274.         main program, immediately after the call to InitIndex.
  275.  
  276.  
  277.  
  278. That's it!  If you have any questions, comments, or trouble with the above
  279. changes, please feel free to contact me on the Borland SIG or via EasyPlex on
  280. CompuServe.  I sincerely hope the above information is useful to you.
  281.  
  282. -- Randy Forgaard
  283.